package com.ruoyi.web.controller.inversion;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.InversionConstants;
import com.ruoyi.common.core.CalcThreadManager;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ProcessStopException;
import com.ruoyi.common.utils.ExecUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SseEmitterUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.CommonUtil;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.dm.domain.po.CollectTleRecordPO;
import com.ruoyi.dm.domain.po.TrackTleConditonPO;
import com.ruoyi.dm.mapper.DataManagementMapper;
import com.ruoyi.dm.service.ICollectTleRecordService;
import com.ruoyi.dm.service.ITrackConditionService;
import com.ruoyi.web.core.config.TleUtil;
import com.ruoyi.web.vo.InversionTleVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.File;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;


/**
 * <p>
 *  大气密度反演 - TLE反演
 * </p>
 *
 * @author liuhl
 * @since 2024-07-05
 */
@RestController
@RequestMapping("/inversion/tle")
@AllArgsConstructor
@Slf4j
@Api(tags = "大气密度反演 - TLE反演")
public class InversionTLEController {

    private ICollectTleRecordService tleRecordService;
    private ITrackConditionService trackConditionService;
    private DataManagementMapper dataManagementMapper;




    @GetMapping("/select")
    @ApiOperation(value = "选取(自动)", notes = "根据时间范围、数量筛选出目标卫星信息和参考卫星信息", httpMethod = "GET")
    public AjaxResult autoSelect(InversionTleVo vo) throws ProcessStopException {


        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        formatter.withZone(ZoneId.of("GMT"));
        String startTime = DateUtil.format(vo.getStartTime(),formatter);
        String endTime = DateUtil.format(vo.getEndTime(),formatter);

        //自动选取
        String targetNum = vo.getTargetNum();
        //从表中查出所有的TLE数据并生成txt
        //-- 数据库查询TLE数据
        List<CollectTleRecordPO> tleDbDataList = tleRecordService.list(new LambdaQueryWrapper<CollectTleRecordPO>()
                //.select(CollectTleRecordPO::getLine1, CollectTleRecordPO::getLine2)
                .between(CollectTleRecordPO::getDataTime, startTime, endTime)
        );
        if (CollUtil.isEmpty(tleDbDataList)){
            return AjaxResult.error(startTime+" - "+endTime+" 范围内不存在TLE数据");
        }
        //-- 生成txt
        String simpleUUID = IdUtil.fastSimpleUUID();
        String baseDir = PathUtil.tmpModuleDir(BizModule.DENSITY_INVERT)
                + "/select_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_FORMAT);
        String tleTxtPath = baseDir + "/input_tle_" + simpleUUID + ".txt";
        File tleFile = new File(tleTxtPath);
        if (tleFile.exists()) {
            tleFile.delete();
        }
        String fileContent = tleDbDataList.stream().map(dbTle -> dbTle.toString()).collect(Collectors.joining("\n"));
        FileWriter.create(tleFile).write(fileContent);

        //修改调用exe的入参
        Map<String, Object> paramMap = MapUtil.newHashMap(true);
        BeanUtil.copyProperties(InversionConstants.TLE_INVERSION_PARAM_MAP, paramMap);
        paramMap.put("allTlePath", tleTxtPath);
        paramMap.put("isFilterTle", true);
        paramMap.put("targetNum", StrUtil.isBlank(targetNum)?"50":Integer.valueOf(targetNum));
        paramMap.put("outputPath", baseDir);


        //conf配置
        String adpPath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_adp");
        String binPath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_bin");
        Map<String,String> confMap = (Map<String,String>)paramMap.get("conf");
        confMap.put("dataPath",adpPath);
        confMap.put("3rdpartyFileDir",binPath);

        //将paramMap转成json文件，调用 exe
        String paramJsonPath = baseDir + "/input_param_" + simpleUUID + ".json";
        File inputJsonFile = new File(paramJsonPath);
        if (inputJsonFile.exists()) {
            inputJsonFile.delete();
        }
        FileWriter.create(inputJsonFile).write(JSONUtil.toJsonPrettyStr(paramMap));

        // 调用tle反演exe执行文件
        String dir = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_dir");
        String exe = PathUtil.moduleDirs().getDensityInvert().get("tle_invert_exe");

        String command = "cd " + dir +" && "+ exe +" "+ paramJsonPath;

        List<String> commandList = Stream.of("bash", "-c", command).collect(Collectors.toList());
        ExecUtil.execPythonNoSendSse(commandList, null);

        //解析 baseDir 下的 两个txt， targetSatTle.txt = n个目标卫星的TLE数据，refSatTle.txt = n个参考卫星的TLE数据
        String refSatTle = baseDir + "/refSatTle.txt";
        String targetSatTle = baseDir + "/targetSatTle.txt";

        //最终返回
        try {
            Map<String, Tuple> targetMap = TleUtil.extractSpecialTleData(targetSatTle);
            Map<String, Tuple> refMap = TleUtil.extractSpecialTleData(refSatTle);
            Map<String,Object> resultMap = new HashMap<>();

            List<Map<String, String>> targetList = targetMap.entrySet().stream().map(entry -> {
                return MapUtil.builder(new HashMap<String, String>())
                        .put("code", entry.getKey())
                        .put("height", entry.getValue().get(0))
                        .put("data", entry.getValue().get(1))
                        .build();
            }).collect(Collectors.toList());

            List<Map<String, String>> refList = refMap.entrySet().stream().map(entry -> {
                return MapUtil.builder(new HashMap<String, String>())
                        .put("code", entry.getKey())
                        .put("height", entry.getValue().get(0))
                        .put("data", entry.getValue().get(1))
                        .build();
            }).collect(Collectors.toList());

            resultMap.put("target", targetList);
            resultMap.put("ref", refList);
            return AjaxResult.success(resultMap);
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error("解析目标卫星和参考卫星数据时异常，tle文件位置："+baseDir);
        }
    }

    @GetMapping("/manualSelect")
    @ApiOperation(value = "选取(手动)", notes = "根据时间范围、卫星编号关键字、筛选出目标卫星信息和参考卫星信息", httpMethod = "GET")
    public AjaxResult manualSelect(InversionTleVo vo) {

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        formatter.withZone(ZoneId.of("GMT"));
        String startTime = DateUtil.format(vo.getStartTime(),formatter);
        String endTime = DateUtil.format(vo.getEndTime(),formatter);
        String tleCode = vo.getTleCode();
        //手动选取
        //从表中查出所有的TLE数据并生成txt
        //-- 数据库查询TLE数据
        List<Map<String,Object>> tleDbDataList = dataManagementMapper.getTleData(startTime, endTime, tleCode);
        if (CollUtil.isEmpty(tleDbDataList)){
            return AjaxResult.error(startTime+" - "+endTime+" 范围内不存在TLE数据");
        }
        //t1.TLE_CODE, t1.LINE1, t1.LINE2
        List<Map<String, Object>> resultList = tleDbDataList.stream().map(dataMap -> {
            return MapUtil.builder(new HashMap<String, Object>())
                    .put("code", dataMap.get("TLE_CODE"))
                    .put("data", dataMap.get("LINE1") + "\n" + dataMap.get("LINE2"))
                    .build();
        }).collect(Collectors.toList());
        return AjaxResult.success(resultList);

    }


    @GetMapping("/invert")
    @ApiOperation(value = "密度反演", notes = "根据时间范围、数量、参考卫星、大气模型，反演出结果", httpMethod = "GET")
    public AjaxResult tleInversion(InversionTleVo vo) throws Exception {

        FutureTask<AjaxResult> task = new FutureTask<>(() ->{
            AjaxResult result = tleInversionInner(vo);
            return result;
        });
        String sseId = vo.getSseId()+SecurityUtils.getLoginUser().getUsername();
        vo.setSseId(sseId);
        CalcThreadManager.addThread(sseId,task);
        try {
            new Thread(task).start();
            return task.get();
        } catch (InterruptedException | CancellationException exception) {
            throw new ProcessStopException();
        } catch (ExecutionException e) {
            if (e.getCause().getClass() == ProcessStopException.class) {
                throw new ProcessStopException();
            }
            throw new Exception(e);
        }
    }


    /**
     * 实际执行密度反演
     * @param vo
     * @return
     * @throws ProcessStopException
     */
    public AjaxResult tleInversionInner(InversionTleVo vo) throws ProcessStopException {

        //TLE和GPS密度反演的结果展示部分需要修改一下，@程雪  @韩佩  @刘欢乐  @刘江凯  大家先看看修改内容，14:30左右16楼当面讨论下。
        //1.GPS密度反演模块，在密度反演的时候不需要选择模型，去掉大气模型的选项。【TLE密度反演有相同改动】
        //
        //-- GPS反演
        //入参：
        //
        //	前端：页面去掉大气模型的选项。
        //	Java：传参去除 大气模型 参数。
        //	算法：没问题。重新给java一个依赖jar
        //
        //结果：
        //
        //	算法：有改动。
        //
        //
        //-- TLE反演
        //入参：
        //
        //	前端：页面去掉大气模型的选项。
        //	Java：传参去除 大气模型 参数。
        //	算法：没问题。
        //
        //结果：
        //
        //	算法：有改动。
        //
        //2.GPS密度反演模块，在密度反演结束后，展示密度反演结果的时候，大气密度轨道分布图上，需要同时展示 反演结果 和 测量结果（两种）。【TLE密度反演有相同改动】
        //
        //
        //-- GPS反演
        //现状：反演结果已经再页面上画出。
        //改动：新增测量结果：StAtmosphereDensityData 增加一个真值密度变量作为测量结果， 测量结果是Y，epoch是X。
        //
        //
        //--TLE反演
        //现状：反演结果已经页面上画出 当地密度 和 有效密度。
        //改动：去掉有效密度，新增真值密度，数据在 density_acc.txt, 包含时间和值，与density.txt的时间不一致，【【【【待定Pending！！！】】】】】
        //
        //
        //3.GPS密度反演模块，在误差分析的时候，需要同时展示反演密度结果和 四种模型，以及真值，的误差结果，“相对误差”标题保持不变，measure保持不变，model扩展为四种模型，相当于支持五条曲线的显示，其中，真值可能为空。“均方根误差”修改为“log(RE)误差”，同样支持五条曲线的显示。【TLE密度反演有相同改动】
        //
        //
        //-- GPS反演
        //
        //- GPS相对误差
        //现状：画了measure和model两条线。
        //改动：去掉model，新增 4条线，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00"，数据来源于 StAtmosphereDensityData 中的 diff_【model名字】_rel。
        //
        //- GPS均方根误差
        //前端：名字修改为 log(RE)误差 。
        //改动：去掉model，新增 4条线，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00"，数据来源于 StAtmosphereDensityData 中的 diff_【model名字】_logre。
        //
        //-- TLE反演
        //
        //- TLE反演相对误差
        //
        //error_loc_acc.txt  //实测的折线图
        //error_loc_【模型名字】.txt  //4个模型的折线图
        //
        //改动：画折线图，按卫星切换，每个卫星包含实测和4个模型共5条折线，折线的X：txt里面第一列的时间，折线的Y：txt里面的第5列。【【【【待定Pending！！！】】】】】
        //
        //
        //{
        //	"20608": {
        //		"tle": {
        //			"x":[],
        //			"y":[]
        //		},
        //		"acc": {
        //			"x":[],
        //			"y":[]
        //		} 或者  空字符串 ""
        //	},
        //	"15589": {
        //		"tle": {
        //			"x":[],
        //			"y":[]
        //		},
        //		"acc": {
        //			"x":[],
        //			"y":[]
        //		}
        //	},
        //
        //}
        //
        //
        //- TLE反演均方根误差
        //
        //logre_loc_acc.txt  //实测的折线图
        //logre_loc_【模型名字】.txt  //4个模型的折线图
        //
        //改名：名字修改为 log(RE)误差 。
        //改动：画折线图，按卫星切换，每个卫星包含实测和4个模型共5条折线，折线的X：txt里面第一列的时间，折线的Y：txt里面的第5列。【【【【待定Pending！！！】】】】】
        //
        //
        //
        //4.误差统计表 修改一下，比较基准分别为  "实测"，"DTM2000"，"HarrisPriester"，"JB2008"，"NRLMSISE00" （与当前界面选项显示内容一致）。 统计结果显示，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差。【TLE密度反演有相同改动】
        //
        //- GPS大气密度反演误差统计表
        //
        //表头更改为：卫星名称 比较基准 相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差  共计6列。
        //取值：把代码给刘博士更改。
        //
        //- TLE大气密度反演误差统计表
        //
        //表头改成：目标编码，密度形式，比较基准，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差 。
        //
        //error_stats.txt 内容如下：
        //
        //密度形式，比较基准，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差 。
        //loc,acc,-,0.2557,0.66845,0.003645
        //loc,DTM2000,0.225447,0.2557,0.66845,0.003645
        //loc,HarrisPriester,...
        //loc,JB2008,...
        //loc,NRLMSISE00,...
        //eff,acc,-,0.2557,0.66845,0.003645
        //eff,DTM2000,0.225447,0.2557,0.66845,0.003645
        //eff,HarrisPriester,...
        //eff,JB2008,...
        //eff,NRLMSISE00,...
        //
        //loc【当地密度】eff【有效密度】
        //
        //算法输出的数值的有效位数谁来处理【【【【待定Pending！！！】】】】】
        //
        //
        //5. TLE反演筛选功能
        //
        //-- 给当前的目标筛选(更改为自动选取) 增加一个互斥的手动选取，手动选取是：根据时间范围、模糊关键字(NORAD编号tleCode) 查询库中已有的卫星TLE数据，每个卫星TLE数据给两行根数就足够。
        //-- 供用户选取的卫星列表页面，表头【code吗】变更为【NORAD 编号】，增加一列高度，高度来源于筛选的目标卫星结果txt和参考卫星结果txt，txt内容修改如下：
        //-- 手动选取的没有高度。
        //
        //
        //00900 //卫星编号
        //高度1 //30  单位是km，前端在表头展示 km
        //1 00900U 64063C   22002.32277604  .00000336  00000-0  34919-3 0  9996
        //2 00900  90.1675  37.9183 0028479 158.2714 310.9985 13.73652174847854
        //01510
        //高度2
        //1 01510U 65065C   22002.59234291  .00001196  00000-0  86429-3 0  9999
        //2 01510  90.0341  16.5587 0037481 281.5382  78.1588 13.95834206805179

        String sseId = vo.getSseId();

        //自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 1,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try{
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            formatter.withZone(ZoneId.of("GMT"));
            String startTime = DateUtil.format(vo.getStartTime(),formatter);
            String endTime = DateUtil.format(vo.getEndTime(),formatter);


            String baseDir = PathUtil.tmpModuleDir(BizModule.DENSITY_INVERT)
                    + "/inversion_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_FORMAT);

            Map<String, Object> paramMap = MapUtil.newHashMap(true);
            BeanUtil.copyProperties(InversionConstants.TLE_INVERSION_PARAM_MAP, paramMap);
            paramMap.put("isFilterTle", false);
            paramMap.put("outputPath", baseDir);

            //conf配置
            String adpPath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_adp");
            String binPath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_bin");
            Map<String,String> confMap = (Map<String,String>)paramMap.get("conf");
            confMap.put("dataPath",adpPath);
            confMap.put("3rdpartyFileDir",binPath);

            //生成参考卫星的tle数据文件
            List<String> refSatesIds = vo.getRefSatesIds();
            Map<String,String> refSatMap = new ConcurrentHashMap<>();
            if (CollUtil.isNotEmpty(refSatesIds)){

                Map<String, String> refTleDataMap = tleRecordService.list(new LambdaQueryWrapper<CollectTleRecordPO>()
                                //.select(CollectTleRecordPO::getLine1, CollectTleRecordPO::getLine2)
                                .in(CollectTleRecordPO::getTleCode, refSatesIds)
                                .between(CollectTleRecordPO::getDataTime, startTime, endTime))
                        .stream()
                        .collect(Collectors.groupingBy(CollectTleRecordPO::getTleCode))
                        .entrySet()
                        .stream()
                        .collect(Collectors.toMap(Map.Entry::getKey, entry -> {
                            return entry.getValue()
                                    .stream()
                                    .sorted(Comparator.comparing(CollectTleRecordPO::getDataTime))
                                    .map(CollectTleRecordPO::toString)
                                    .collect(Collectors.joining("\n"));
                        }));
                //校验一下
                List<String> nonExitsRefKeys = refSatesIds.stream().filter(o -> !refTleDataMap.containsKey(o)).collect(Collectors.toList());
                if (CollUtil.isNotEmpty(nonExitsRefKeys)){
                    return AjaxResult.error("以下参考卫星没有查询到TLE数据: " + String.join(",", nonExitsRefKeys));
                }
                //并发生成文件
                CompletableFuture.allOf(refTleDataMap.entrySet().stream().map(entry -> {
                    return CompletableFuture.runAsync(() -> {
                        String tleCode = entry.getKey();
                        String tleTxtContent = entry.getValue();
                        String refTxtPath = baseDir + "/ref_tle_" + tleCode + ".txt";
                        FileWriter.create(new File(refTxtPath)).write(tleTxtContent);
                        refSatMap.put(tleCode, refTxtPath);
                    }, pool);
                }).toArray(CompletableFuture[]::new)).thenRun(() -> {
                    paramMap.put("refSat", refSatMap);
                }).join();
            }

            List<String> targetSatesIds = vo.getTargetSatesIds();
            Map<String,String> targetSatMap = new ConcurrentHashMap<>();
            //从表中查出所有的TLE数据并生成txt
            //-- 数据库查询TLE数据, 并根据编号分组
            List<CollectTleRecordPO> tleDbDataList = tleRecordService.list(new LambdaQueryWrapper<CollectTleRecordPO>()
                    //.select(CollectTleRecordPO::getLine1, CollectTleRecordPO::getLine2)
                    .in(CollectTleRecordPO::getTleCode, targetSatesIds)
                    .between(CollectTleRecordPO::getDataTime, startTime, endTime)
            );
            if (CollUtil.isEmpty(tleDbDataList)){
                return AjaxResult.error(startTime+" - "+endTime+" 范围内不存在TLE数据");
            }
            //字典 <tleCode, tleData字符串>
            Map<String,String> targetTleDataMap = tleDbDataList.stream()
                    .collect(Collectors.groupingBy(CollectTleRecordPO::getTleCode))
                    .entrySet()
                    .stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> {
                        return entry.getValue()
                                .stream()
                                .sorted(Comparator.comparing(CollectTleRecordPO::getDataTime))
                                .map(CollectTleRecordPO::toString)
                                .collect(Collectors.joining("\n"));
                    }));

            //校验一下
            List<String> nonExitsTargetKeys = targetSatesIds.stream().filter(o -> !targetTleDataMap.containsKey(o)).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(nonExitsTargetKeys)){
                return AjaxResult.error("以下目标卫星没有查询到TLE数据: " + String.join(",", nonExitsTargetKeys));
            }


            //并发生成文件
            CompletableFuture.allOf(targetTleDataMap.entrySet().stream().map(entry -> {
                return CompletableFuture.runAsync(() -> {
                    String tleCode = entry.getKey();
                    String tleTxtContent = entry.getValue();
                    String targetTxtPath = baseDir + "/target_tle_" + tleCode + ".txt";
                    FileWriter.create(new File(targetTxtPath)).write(tleTxtContent);
                    targetSatMap.put(tleCode, targetTxtPath);
                }, pool);
            }).toArray(CompletableFuture[]::new)).join();
            targetSatMap.entrySet().forEach(entry -> log.info("targetSatMap: "+entry.getKey()+" >> "+entry.getValue()));

            //准备密度数据，密度数据可以没有，查不到就是空
            //字典 <tleCode, 表名称>
            // 表前缀 SWDATA_OBS_DEN_*
            String tablePrefix = "SWDATA_OBS_DEN_";
            Map<String, String> tleCodeAndTableMap = trackConditionService.list(new LambdaQueryWrapper<TrackTleConditonPO>()
                            .in(TrackTleConditonPO::getNoradCatId, targetSatesIds))
                    .stream()
                    .collect(Collectors.toMap(TrackTleConditonPO::getNoradCatId, o -> {
                        String suffix = o.getObjectName().replaceAll(" ", "");
                        return tablePrefix + suffix;
                    }));
            //并发生成文件
            String densityTxtDelimiter = "     "; //5个空格
            String densityTxtContentTitle = "Time(UTC),  Altitude(m),  Geodetic LON(degree),  Geodetic LAT(degree),  Density(kg/m3) \n";
            Map<String,String> densityMap = new ConcurrentHashMap<>();
            CompletableFuture.allOf(tleCodeAndTableMap.entrySet().stream().map(entry -> {
                return CompletableFuture.runAsync(() -> {
                    String tleCode = entry.getKey();
                    String tableName = entry.getValue();
                    try{
                        List<Map<String, Object>> densityDbList = dataManagementMapper.getDensityData(tableName,startTime,endTime);
                        String densityTxtContentBody = densityDbList.stream().map(map -> {

                            return Stream.of(
                                    map.get("TIME").toString(),
                                    map.get("ALT").toString(),
                                    map.get("LON").toString(),
                                    map.get("LAT").toString(),
                                    map.get("DENSITY").toString()
                            ).collect(Collectors.joining(densityTxtDelimiter));
                        }).collect(Collectors.joining("\n"));
                        if (densityTxtContentBody == null || StrUtil.isBlank(densityTxtContentBody.trim())){
                            return;
                        }
                        String densityContent = densityTxtContentTitle + densityTxtContentBody;
                        String densityPath = baseDir + "/obs_density_" + tleCode + ".txt";
                        FileWriter.create(new File(densityPath)).write(densityContent);
                        densityMap.put(tleCode, densityPath);
                    }catch (Exception e){
                        e.printStackTrace();
                        log.info("查询数据库表: {} 时出错, 卫星编码:{}, 异常信息:{}", tableName, tleCode,e.getMessage());
                    }
                }, pool);
            }).toArray(CompletableFuture[]::new)).join();

            //没有密度数据的卫星 打印一下日志
            List<String> nonExitsDensitySates = targetSatesIds.stream().filter(o -> !densityMap.containsKey(o)).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(nonExitsDensitySates)){
                log.info("以下目标卫星没有查询到关联的密度数据表或者数据: " + String.join(",", nonExitsDensitySates));
            }

            //isStop 表示进程是否终止
            AtomicBoolean isStop = new AtomicBoolean(false);
            CompletableFuture.allOf(targetSatesIds.stream().map(sateCode -> {
                return CompletableFuture.runAsync(() -> {

                    Map<String, Object> newParamMap = MapUtil.newHashMap(true);
                    BeanUtil.copyProperties(paramMap, newParamMap);

                    String tlePath = targetSatMap.get(sateCode);
                    newParamMap.put("allTlePath", tlePath);
                    newParamMap.put("targetNo", sateCode);
                    newParamMap.put("obsDensityFilePath", densityMap.get(sateCode) == null? "": densityMap.get(sateCode));

                    String paramJsonPath = baseDir + "/input_param_" + sateCode + ".json";
                    FileWriter.create(new File(paramJsonPath)).write(JSONUtil.toJsonPrettyStr(newParamMap));

                    // 调用tle反演exe执行文件
                    String dir = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("tle_invert_dir");
                    String exe = PathUtil.moduleDirs().getDensityInvert().get("tle_invert_exe");
                    String command = "cd " + dir + " && " + exe + " " + paramJsonPath;

                    List<String> commandList = Stream.of("bash", "-c", command).collect(Collectors.toList());

                    Map<String, String> placeholder = MapUtil.builder(new HashMap<String, String>()).put(sateCode, "PLACEHOLDER").build();
                    String msgTemplate = JSONUtil.toJsonStr(placeholder);
                    try {
                        String results = ExecUtil.execPythonWithMsgTemplate(commandList, sseId, msgTemplate);
                    } catch (ProcessStopException e) {
                        //进程池 不再接受任务
                        pool.shutdownNow();
                        isStop.set(true);
                    }
                }, pool);
            }).toArray(CompletableFuture[]::new)).join();

            if (isStop.get()) {
                throw new ProcessStopException("动态修正系数计算评估已终止");
            }


            // 生成的数据要给程雪看看，接下来要调用她那边。
            // 在baseDir下会每个卫星生成一个目录，目录名=卫星编号，目录下是密度和误差结果文件
            // 调用程雪脚本 python new_dataFusion.py SWDATA_OBS_DEN_GRACEB_XXXX.txt /dgdq/tmp/dataFusion/newtest 2005-01-02

            List<String> dirNames = Arrays.stream(FileUtil.ls(baseDir)).map(File::getName).collect(Collectors.toList());
            log.info("目录: {}下获取到的目录列表: {}",baseDir,String.join(", ",dirNames));

            //给前端要返回：
            //1.pic  : 按时间的
            //2.密度数据：分卫星的，每个卫星两条线
            //3.相对误差、均方根误差，按照卫星切换，每个卫星5条线或者4条线
            //4.误差统计表: error_stats.txt 解析出来： 密度形式，比较基准，相对误差均值，相对误差标准差，log(RE) 误差均值，log(RE) 误差标准差

            Map<String, List<Tuple>> locDensityDataMap = new ConcurrentHashMap<>(); //tle的code-density数据 当地密度
            Map<String, List<Tuple>> accDensityDataMap = new ConcurrentHashMap<>(); //acc的code-density 真值密度数据

            Map<String, Object> relativeErrorBySateMap = new ConcurrentHashMap<>(); //code-相对误差
            Map<String, Object> rootErrorBySateMap = new ConcurrentHashMap<>();     //code-均方根误差

            List<Map<String,String>> tableList = Collections.synchronizedList(new ArrayList<>());

            List<String> filePathList = Collections.synchronizedList(new ArrayList<>());//结果保存所需的文件路径列表
            List<String> models = vo.getModels();

            CompletableFuture.allOf(dirNames.stream().filter(targetSatesIds::contains).map(name -> {

                //每个目录下，最终要能获得 以下几种文件：
                //1. density_loc.txt、density_acc.txt 密度数据文件 ， 为了画图
                //2.1 误差都是按卫星切换的
                //2.2 相对误差：error_loc_acc.txt  //实测的折线图,  error_loc_【模型名字】.txt  //4个模型的折线图
                //2.3 均方根误差：logre_loc_acc.txt  //实测的折线图， logre_loc_【模型名字】.txt  //4个模型的折线图
                //3. 统计表 error_stats.txt 不按卫星切换

                return CompletableFuture.runAsync(() -> {
                    try {
                        String tleDensityTxtPath = baseDir + "/" + name + "/density_loc.txt"; //当地密度
                        String accDensityTxtPath = baseDir + "/" + name + "/density_acc.txt"; //真值密度 //可能不存在

                        //相对误差
                        List<String> relativeErrorFilePaths = models.stream().map(o -> baseDir + "/" + name + "/Error_loc_" + o + ".txt").collect(Collectors.toList());
                        String relativeErrorFilePathAcc = baseDir + "/" + name + "/Error_loc_acc.txt"; //可能不存在

                        //均方根误差
                        List<String> rootErrorFilePaths = models.stream().map(o -> baseDir + "/" + name + "/logre_loc_" + o + ".txt").collect(Collectors.toList());
                        String rootErrorFilePathAcc = baseDir + "/" + name + "/logre_loc_acc.txt"; //可能不存在

                        //统计表
                        String statsPath = baseDir + "/" + name + "/error_stats.txt";

                        List<String> mustExistsFilePaths = CollUtil.newArrayList(tleDensityTxtPath, statsPath);
                        checkFileExists(mustExistsFilePaths);
                        checkFileExists(relativeErrorFilePaths);
                        checkFileExists(rootErrorFilePaths);

                        filePathList.addAll(mustExistsFilePaths);
                        filePathList.addAll(relativeErrorFilePaths);
                        filePathList.addAll(rootErrorFilePaths);

                        //密度
                        List<Tuple> tleDensityTupleList = CommonUtil.readDataByStart(tleDensityTxtPath, 2, null)
                                .stream()
                                .map(o -> {
                                    return new Tuple(o[0], o[1], o[2], o[3], o[4]); //时间，经度，纬度，高度，密度
                                })
                                .sorted(Comparator.comparing(o -> o.get(0)))
                                .collect(Collectors.toList());
                        locDensityDataMap.put(name, tleDensityTupleList);

                        //相对误差
                        Map<String, Object> relativeModelAndXYMap = models.stream().map(model -> {
                            String filePath = baseDir + "/" + name + "/Error_loc_" + model + ".txt";
                            List<Tuple> modelErrorList = CommonUtil.readDataByStart(filePath, 2, null)
                                    .stream()
                                    .map(o -> {
                                        return new Tuple(o[0], o[4]); //时间，经度，纬度，高度，误差
                                    })
                                    .sorted(Comparator.comparing(o -> o.get(0)))
                                    .collect(Collectors.toList());
                            List<Object> xList = modelErrorList.stream().map(o -> o.get(0)).collect(Collectors.toList());
                            List<Object> yList = modelErrorList.stream().map(o -> o.get(1)).collect(Collectors.toList());
                            Map<String, Object> xAndYMap = MapUtil.builder(new HashMap<String, Object>())
                                    .put("x", xList)
                                    .put("y", yList)
                                    .build();
                            return new Tuple(model, xAndYMap);
                        }).collect(Collectors.toMap(o -> o.get(0), o -> o.get(1)));

                        //均方根误差
                        Map<String, Object> rootModelAndXYMap = models.stream().map(model -> {
                            String filePath = baseDir + "/" + name + "/logre_loc_" + model + ".txt";
                            List<Tuple> modelErrorList = CommonUtil.readDataByStart(filePath, 2, null)
                                    .stream()
                                    .map(o -> {
                                        return new Tuple(o[0], o[4]); //时间，经度，纬度，高度，误差
                                    })
                                    .sorted(Comparator.comparing(o -> o.get(0)))
                                    .collect(Collectors.toList());
                            List<Object> xList = modelErrorList.stream().map(o -> o.get(0)).collect(Collectors.toList());
                            List<Object> yList = modelErrorList.stream().map(o -> o.get(1)).collect(Collectors.toList());
                            Map<String, Object> xAndYMap = MapUtil.builder(new HashMap<String, Object>())
                                    .put("x", xList)
                                    .put("y", yList)
                                    .build();
                            return new Tuple(model, xAndYMap);
                        }).collect(Collectors.toMap(o -> o.get(0), o -> o.get(1)));

                        //如果acc密度存在
                        if (!nonExitsDensitySates.contains(name)) {
                            //accDensityTxtPath
                            //relativeErrorFilePathAcc
                            //rootErrorFilePathAcc

                            filePathList.add(accDensityTxtPath);
                            filePathList.add(relativeErrorFilePathAcc);
                            filePathList.add(rootErrorFilePathAcc);

                            //密度
                            List<Tuple> accDensityTupleList = CommonUtil.readDataByStart(accDensityTxtPath, 2, null)
                                    .stream()
                                    .map(o -> {
                                        return new Tuple(o[0], o[1], o[2], o[3], o[4]); //时间，经度，纬度，高度，密度
                                    })
                                    .sorted(Comparator.comparing(o -> o.get(0)))
                                    .collect(Collectors.toList());
                            accDensityDataMap.put(name, accDensityTupleList);

                            //相对误差
                            List<Tuple> modelErrorList = CommonUtil.readDataByStart(relativeErrorFilePathAcc, 2, null)
                                    .stream()
                                    .map(o -> {
                                        return new Tuple(o[0], o[4]); //时间，经度，纬度，高度，误差
                                    })
                                    .sorted(Comparator.comparing(o -> o.get(0)))
                                    .collect(Collectors.toList());
                            List<Object> xList = modelErrorList.stream().map(o -> o.get(0)).collect(Collectors.toList());
                            List<Object> yList = modelErrorList.stream().map(o -> o.get(1)).collect(Collectors.toList());
                            Map<String, Object> xAndYMap = MapUtil.builder(new HashMap<String, Object>())
                                    .put("x", xList)
                                    .put("y", yList)
                                    .build();
                            relativeModelAndXYMap.put("acc", xAndYMap);

                            //均方根误差
                            List<Tuple> rootErrorList = CommonUtil.readDataByStart(rootErrorFilePathAcc, 2, null)
                                    .stream()
                                    .map(o -> {
                                        return new Tuple(o[0], o[4]); //时间，经度，纬度，高度，误差
                                    })
                                    .sorted(Comparator.comparing(o -> o.get(0)))
                                    .collect(Collectors.toList());
                            List<Object> xListRoot = rootErrorList.stream().map(o -> o.get(0)).collect(Collectors.toList());
                            List<Object> yListRoot = rootErrorList.stream().map(o -> o.get(1)).collect(Collectors.toList());
                            Map<String, Object> xAndYMapRoot = MapUtil.builder(new HashMap<String, Object>())
                                    .put("x", xListRoot)
                                    .put("y", yListRoot)
                                    .build();
                            rootModelAndXYMap.put("acc", xAndYMapRoot);
                        }

                        relativeErrorBySateMap.put(name, relativeModelAndXYMap);
                        rootErrorBySateMap.put(name, rootModelAndXYMap);

                        //统计表
                        List<String> dataList = CommonUtil.readLineDataByStart(new File(statsPath), 1, null);
                        //densityType,CompareBase,MeanRms,StdRms,MeanLogRe,StdLogRe
                        List<String> titles = Arrays.stream(dataList.get(0).split(",")).collect(Collectors.toList());

                        List<Map<String, String>> statsList = dataList
                                .stream()
                                .skip(1)
                                .map(e -> {
                                    List<String> data = Arrays.stream(e.split(",")).collect(Collectors.toList());
                                    Map<String, String> dataMap = new HashMap<>();
                                    dataMap.put("tleCode", name);
                                    IntStream.range(0, data.size()).forEach(i -> dataMap.put(titles.get(i), data.get(i)));
                                    return dataMap;
                                }).collect(Collectors.toList());
                        tableList.addAll(statsList);
                    }catch (Exception e) {
                        log.error("",e);
                    }
                },pool);
            }).toArray(CompletableFuture[]::new)).join();


            //准备数据调用程雪python获取图片
            // 调用程雪脚本 python new_dataFusion.py SWDATA_OBS_DEN_GRACEB_XXXX.txt /dgdq/tmp/dataFusion/newtest 2005-01-02
            // 程雪的脚本是一天的数据 调用一次
            Map<String, List<Tuple>> inputParamDataMap = locDensityDataMap.values()
                    .stream()
                    .flatMap(List::stream)
                    .collect(HashMap<String, List<Tuple>>::new, (map, tuple) -> {
                        String key = tuple.get(0).toString().substring(0, 10); // 2006-10-01
                        List<Tuple> tuples = map.get(key);
                        if (tuples == null) {
                            tuples = new ArrayList<>();
                            tuples.add(tuple);
                            map.put(key, tuples);
                        } else {
                            tuples.add(tuple);
                        }
                    }, HashMap::putAll);//串行流的第三个combiner不会执行


            String txtDelimiter = ",";
            String txtTitle = "Time,effectAtmosphereResultLon,effectAtmosphereResultLat,effectAtmosphereResultH,effectAtmosphereResultDensityData \n";
            Map<String,String> dayAndPicMap = new ConcurrentHashMap<>();
            CompletableFuture.allOf(inputParamDataMap
                    .entrySet()
                    .stream()
                    .map(entry -> {
                        return CompletableFuture.runAsync(() -> {

                            String day = entry.getKey();
                            List<Tuple> densityDataTuples = entry.getValue().stream()
                                    .sorted(Comparator.comparing(t -> t.get(0)))
                                    .collect(Collectors.toList());

                            //2024-8-26 程雪认为给的密度太密集，导致算法画图耗时太长，要求密度数据筛选，
                            String txtBody = IntStream.range(0,densityDataTuples.size())
                                    .filter(i -> i%10 ==0)
                                    .mapToObj(index -> {
                                        return densityDataTuples.get(index).stream().map(StrUtil::toString).collect(Collectors.joining(txtDelimiter));
                                    })
                                    .collect(Collectors.joining("\n"));

                            String txtContent = txtTitle + txtBody;
                            String densityPath = baseDir + "/get_pic_params_" + day + ".txt";
                            FileWriter.create(new File(densityPath)).write(txtContent);

                            //python new_dataFusion.py SWDATA_OBS_DEN_GRACEB_XXXX.txt /dgdq/tmp/dataFusion/newtest 2005-01-02
                            String codePath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("get_pic_path");
                            String command = "python " + codePath + " " + densityPath + " " + baseDir + " " + day;
                            String result = ExecUtil.execCmdWithResult(command);
                            if (result == null || !result.contains("###")) {
                                throw new RuntimeException("调用获取图片脚本时异常，结果: " + (result == null ? "空" : result));
                            }
                            String picPath = result.split("###")[1].trim();
                            dayAndPicMap.put(day, picPath);
                        }, pool);
                    }).toArray(CompletableFuture[]::new)).join();

            //准备给前端返回的数据结构
            Map<String,Object> resultMap = new HashMap<>();
            //1. 图片按日期升序返回
            TreeMap<String, String> sortedPicMap = MapUtil.sort(dayAndPicMap);
            resultMap.put("pic", sortedPicMap);

            //2. 密度分目标
            Map<String,Object> chartMap = new HashMap<>();

            locDensityDataMap.entrySet().stream().forEach(entry -> {
                String tleCode = entry.getKey();
                List<String> xTime = entry.getValue().stream().map(o -> o.get(0).toString()).collect(Collectors.toList());
                List<String> yDensity = entry.getValue().stream().map(o -> o.get(4).toString()).collect(Collectors.toList());
                Map<String, List<String>> xAndY = MapUtil.builder(new HashMap<String, List<String>>())
                        .put("x", xTime)
                        .put("y", yDensity)
                        .build();
                Map<String,Object> densityChartMap = MapUtil.builder(new HashMap<String, Object>())
                        .put("loc", xAndY)
                        .build();

                List<Tuple> accTuples = accDensityDataMap.get(tleCode);
                if (accTuples != null){
                    //将数据转为时间，页面统一转为北京时
                    List<Date> xTimeAcc = accTuples.stream().map(o -> DateUtil.parse(o.get(0).toString(),"yyyy-MM-dd HH:mm:ss")).collect(Collectors.toList());
                    List<String> yDensityAcc = accTuples.stream().map(o -> o.get(4).toString()).collect(Collectors.toList());
                    Map<String,Object> xAndYAcc = MapUtil.builder(new HashMap<String, Object>())
                            .put("x", xTimeAcc)
                            .put("y", yDensityAcc)
                            .build();
                    densityChartMap.put("acc", xAndYAcc);
                }
                chartMap.put(tleCode,densityChartMap);
            });

            resultMap.put("chart", chartMap);

            //3. 误差分析 - 相对误差 均方根误差
            resultMap.put("error_relative", relativeErrorBySateMap);
            resultMap.put("error_root", rootErrorBySateMap);

            //4. 表格数据
            resultMap.put("table", tableList);

            resultMap.put("resultData", JSONUtil.toJsonStr(filePathList));
            resultMap.put("module", ResultSaveModule.DENSITY_INVERT);
            resultMap.put("submodule", ResultSaveModule.DENSITY_INVERT_TLE);
            return AjaxResult.success(resultMap);
        }finally {

            pool.shutdown();
            //600 终止标志
            SseEmitterUtils.sendMessage(sseId, "600");
            SseEmitterUtils.remove(sseId);

        }
    }

    @ApiOperation(value = "建立连接", notes = "建立连接")
    @GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter push(String id, String flag) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return SseEmitterUtils.connect(id+loginUser.getUsername(), flag);
    }


    private void checkFileExists(List<String> paths){
        paths.forEach(o -> {
            File file = new File(o);
            if (!file.exists()){
                String[] split = o.split("/");
                String name = split[split.length-2];
                String errorMsg = "";
                if (o.contains("density_")){
                    errorMsg = name+" 反演的密度文件不存在, 文件路径: "+o;
                }else if (o.contains("error_")){
                    errorMsg = name+" 反演的误差文件不存在, 文件路径: "+o;
                }else {
                    errorMsg = name+"文件不存在, 文件路径: "+o;
                }
                throw new RuntimeException(errorMsg);
            }
        });
    }

}
